package com.youlai.system.common.aspect;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.youlai.system.common.annotation.Log;
import com.youlai.system.common.enums.HttpMethodEnum;
import com.youlai.system.common.manager.AsyncManager;
import com.youlai.system.common.util.*;
import com.youlai.system.mapper.SysOperLogMapper;
import com.youlai.system.model.entity.SysOperLog;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Map;

/**
 * @description: 日志切面
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    @Resource
    private SysOperLogMapper sysOperLogMapper;

    @Resource
    private ObjectMapper objectMapper;


    /** 计算操作消耗时间 */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");

    @Before(value = "@annotation(log)")
    public void before(JoinPoint joinPoint, Log log) {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    /**
     * 方法返回后的通知
     */
    @AfterReturning(value = "@annotation(log)", returning = "resultJson")
    public void doAfterReturning(JoinPoint joinPoint, Log log, Object resultJson) {
        handleOperLog(joinPoint, log, null, resultJson);
    }

    /**
     * 抛出异常后的通知
     */
    @AfterThrowing(value = "@annotation(log)", throwing = "e")
    private void doAfterThrowing(JoinPoint joinPoint, Log log, Exception e) {
        handleOperLog(joinPoint, log, e, null);
    }


    /**
     * 操作日志
     */
    private void handleOperLog(JoinPoint joinPoint, Log sysLog, final Exception e, Object jsonResult) {
        SysOperLog operLog = new SysOperLog().setStatus(0);
        try {
            operLog.setOperIp(IpUtils.getIpAddr())
                    .setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255))
                    .setOperName(UserInfoUtils.getUserName())
                    .setDeptId(String.valueOf(UserInfoUtils.getDeptId()))
                    .setOperatorType(sysLog.operatorType().ordinal())
                    .setModule(sysLog.module().name())
                    .setTitle(sysLog.title())
                    .setBusinessType(sysLog.businessType().type)
                    .setMethod(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()")
                    .setRequestMethod(ServletUtils.getRequest().getMethod())
                    .setOperParam(getOperParam(sysLog.isSaveRequestData(), joinPoint))
                    .setJsonResult(getJsonResult(sysLog.isSaveResponseData(), jsonResult))
                    .setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get())
                    .setOperTime(LocalDateTime.now());

            if (ObjUtils.isNotNull(e)) {
                operLog.setStatus(1);
                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            // 异步插入
            asyncInsertData(operLog);
        } catch (Exception ex) {
            log.error("ERROR LogAspect.handleOperLog 异常 信息:", ex);
        } finally {
            TIME_THREADLOCAL.remove();
        }
    }

    private void asyncInsertData(SysOperLog sysOperLog) {

        AsyncManager.me().execute(() -> {
            // 远程查询操作地点
            sysOperLog.setOperLocation(AddressUtils.getRealAddressByIP(sysOperLog.getOperIp()));
            sysOperLogMapper.insert(sysOperLog);
        });
    }

    private String getOperParam(boolean saveRequestData, JoinPoint joinPoint) throws JsonProcessingException {
        if (!saveRequestData) {
            return null;
        }
        Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
        String requestMethod = ServletUtils.getRequest().getMethod();
        if (ObjUtils.isEmpty(paramsMap)
                && (HttpMethodEnum.PUT.name().equals(requestMethod) || HttpMethodEnum.POST.name().equals(requestMethod))) {
            String params = argsArrayToString(joinPoint.getArgs());
            return StringUtils.substring(params, 0, 2000);
        } else {
            return StringUtils.substring(objectMapper.writeValueAsString(paramsMap), 0, 2000);
        }
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (ObjUtils.isNotEmpty(paramsArray)) {
            for (Object o : paramsArray) {
                if (ObjUtils.isNotNull(o) && !isFilterObject(o)) {
                    try {
                        String jsonObj = objectMapper.writeValueAsString(o);
                        params.append(jsonObj).append(" ");
                    } catch (Exception ignored) {
                    }
                }
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }

    private String getJsonResult(boolean saveResponseData, Object jsonResult) throws JsonProcessingException {
        if (!(saveResponseData && ObjUtils.isNotEmpty(jsonResult))) {
            return null;
        }
        return StringUtils.substring(objectMapper.writeValueAsString(jsonResult), 2000);
    }
}
